/* Copyright (C) 2010 Moritz Bitsch
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 *
 * Author:
 *      Moritz Bitsch <moritzbitsch@googlemail.com>
 */

namespace Webappi {
	public class Session {
		//Global session storage
		private static HashTable<string, Session> sessions;
		private static uint lastCleanup;
		private scgi.Request req;

		public string sid { get; private set; } //session id
		public Object data {get; set; } //data for this session
		public uint timestamp { get; private set; } //timestamp needed for cleanup

		public Session(scgi.Request req) {
			//initialize global session store on first usage
			lock (sessions) {
				if (sessions == null) {
					sessions = new HashTable<string,Session>.full(str_hash, str_equal, g_free, Session.unref);
				}

				this.req = req;

				this.sid = generateSID();

				while (sessions.lookup(this.sid) != null) {
					this.sid = generateSID();
				}

				this.save();
			}
		}

		private string generateSID() {
			StringBuilder builder = new StringBuilder();

			for (int i = 0; i<32; i++) {
				var c = Random.int_range(1,27);
				builder.append_c((char)(96 + c));
			}

			return builder.str;
		}

		/*
		 * To prevent cookie stealing we hash the sid value togehter with the remote host
		 */
		private static string mangleSID(scgi.Request req, string sid) {
			string remoteHost = req.params.lookup("REMOTE_ADDR");

			var remoteHostBytes = remoteHost.to_utf8();
			var sidBytes = sid.to_utf8();

			Checksum csum = new Checksum(ChecksumType.SHA1);

			csum.update((uchar[])remoteHostBytes, remoteHostBytes.length);
			csum.update((uchar[])sidBytes, sidBytes.length);

			return csum.get_string();
		}

		public void save() {
			this.timestamp = (uint)time_t();
			lock(sessions) {
				sessions.insert(mangleSID(this.req, this.sid), this);
			}
		}

		public void delete() {
			lock(sessions) {
				sessions.remove(mangleSID(this.req, this.sid));
			}
		}

		public static Session? load(scgi.Request req, string sid) {
			//initialize global session store on first usage
			Session session = null;
			lock (sessions) {
				if (sessions == null) {
					sessions = new HashTable<string,Session>.full(str_hash, str_equal, g_free, Session.unref);
				}

				session = sessions.lookup(mangleSID(req, sid));

				if (session != null) {
					session.timestamp = (uint)time_t();
				}
			}
			return session;
		}

		/*
		 * This function is called by the application to remove the old sessions
		 */
		public static void cleanup() {
			lock (sessions) {
				if (sessions == null)
					return;

				var now = (uint)time_t();

				if (lastCleanup > (now - (60 * 5)))
					return;

				dprint(LogLevel.Debug, "SESSION", "Starting cleanup");

				lastCleanup = now;

				foreach (string key in sessions.get_keys()) {
					var session = sessions.lookup(key);


					if (session.timestamp < (now - (60 * 20))) {
						sessions.remove(key);
					}
				}

				dprint(LogLevel.Debug, "SESSION", "Cleanup complete");
			}
		}

		//unref function for hashtable
		public static extern new void unref(void* self);
	}
}
